// 解析Vue并进行DOM操作
class Compiler {
  // 构造函数传入vue实例
  constructor (vm) {
    // 创建相应属性
    this.el = vm.$el
    this.vm = vm
    // Vue实例调用Compiler则开始处理DOM
    this.compile(this.el)
  }

  // 编译模板，处理文本节点和元素节点
  compile (el) {
    // 获取所有的子节点  childNodes为子节点  children为子元素 
    let childNodes = el.childNodes
    // 将伪数组转换为数组  并遍历数组中的每一个节点
    // 此处仅为一层子节点
    Array.from(childNodes).forEach(node => {
      // 此处箭头函数中的this为compiler实例
      // 处理文本节点
      if(this.isTextNode(node)){
        this.compileText(node)
      }else if (this.isElementNode) {
        // 处理元素节点
        this.compileElement(node)
      }

      // 判断node节点，是否有子节点，如果有子节点，要递归调用compile
      if(node.childNodes && node.childNodes.length) {
        this.compile(node)
      }
    })
  }

  // 编译元素节点，处理指令
  compileElement (node) {
    // 遍历所有的属性节点  node.attributes获取属性节点
    Array.from(node.attributes).forEach(attr => {
      // 获取属性名字
      let attrName = attr.name
      // 判断是否是指令
      if (this.isDirective(attrName)){
        // v-text --> text  截取字符串
        attrName = attrName.substr(2)
        // 获取属性值
        let key = attr.value
        this.update(node, key, attrName)
      }
    })
  }

  // 调用对应指令的方法
  update (node, key, attrName) {
    let cmd, fn
    // v-on:click 等情况
    if(attrName.match(':')){
      // 获取指令
      cmd = attrName.split(':')[0]
      // 获取事件
      fn = attrName.split(':')[1]
    }
    else {
      cmd = attrName
    }
    let updateFn = this[cmd + 'Updater']
    // 指令方法存在则调用该方法
    updateFn && updateFn.call(this, node, this.vm[key], key, fn)
  }

  // 处理 v-text 指令
  textUpdater (node, value, key) {
    // 将节点的文本内容更改为Vue属性值
    node.textContent = value

    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue
    })
  }
  // v-model
  modelUpdater (node, value, key) {
    // 表单元素的值 value 等于Vue属性值
    node.value = value
    //双向绑定 监听节点中的输入框
    node.addEventListener('input', () => {
      // 触发属性set机制 发布者通知所有的观察者执行函数  更新DOM视图
      this.vm[key] = node.value
    })

    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue
    })
  }

  // v-html
  htmlUpdater (node, value, key) {
    node.innerHTML = value
    
    new Watcher(this.vm, key, (newValue) => {
      node.innerHTML = newValue
    })
  }

  // v-on

  onUpdater (node, value, key, fn) {
    // 获取Vue实例中定义的方法
    let methods = this.vm.$options.methods

    // 正则判断值为伪对象情况
    // v-on = "{click: clickFn, mouseout: move}"
    let reg = /\{(.+?)\}/
    if (reg.test(key)) {
      // 提取事件匹配内容
      // 获取$num 为第num个小括号()分组的内容
      let str = RegExp.$1.trim()
      // 分割每个事件规则
      let arr = str.split(',')
      // 循环事件规则数组
      arr.forEach(e => {
        // 将事件规则拆成 事件名称 以及 调用的方法名称
        let map = e.split(':')
        // 事件名称
        let eName = map[0].trim()
        // 调用的方法名称
        let eFn = map[1].trim()
        // Vue实例存在该方法 则监听该事件
        if (methods[eFn]) {
          node.addEventListener(eName, methods[eFn])
        }
        else{
          console.log(new Error(`${eFn} is undefined`)); 
        }
      })
    }
    // v-on:click="clickFn" 等情况
    else if (fn) {
      // Vue实例存在该方法 则监听该事件
      if (methods[key]) {
        node.addEventListener(fn, methods[key])
      }
      else{
        console.log(new Error(`${key} is undefined`)); 
      }
    }
  }

  // 编译文本节点，处理差值表达式
  compileText (node) {
    // {{  msg }}
    // . 匹配任意字符不包括换行 | + 前面的内容出现一或多次 | ？非贪婪模式 尽快结束匹配 | () 分组含义 提取括号中的匹配内容
    let reg = /\{\{(.+?)\}\}/
    // 文本节点中的内容 使用 node.textContent / node.nodeValue 获取
    let value = node.textContent
    if(reg.test(value)){
      // $num 为第num个小括号()分组的内容
      let key = RegExp.$1.trim()
      // 将正则匹配内容替换为Vue实例属性中的值
      node.textContent = value.replace(reg, this.vm[key])

      // 创建watcher对象，当数据改变更新视图
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue
      })
    }
  }
  // 判断元素属性是否是指令
  isDirective (attrName) {
    // 判断属性是否以 ‘v-’开头
    return attrName.startsWith('v-')
  }
  // 判断节点是否是文本节点
  isTextNode (node) {
    // 节点有nodeType 节点类型以及nodeValue 节点值
    // nodeType值为 3是文本节点 1是元素节点  2为属性节点
    return node.nodeType === 3
  }
  // 判断节点是否是元素节点
  isElementNode (node) {
    return node.nodeType === 1
  }
}